﻿// *----------------------------------------------------------------
// Copyright (C) 2017 通通优品
// 版权所有。
// 
// 类名：RedisCache.cs
// 功能描述：TongTongMall.Redis
// 
// Create User：jym 2017/02/09
// 
// Edit User：UserName,EditTime
// Describe：Describe
// ----------------------------------------------------------------*/
using System;

using StackExchange.Redis;
using TongTongMall.Domain;
using TongTongMall.Runtime.Caching;
using TongTongMall.Json;
using Newtonsoft.Json;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using Nito.AsyncEx;

namespace TongTongMall.Redis.Cache
{
    public class RedisCache : CacheBase, IRedisCache
    {
        /// <summary>
        /// redis服务对象
        /// </summary>
        private readonly IDatabase _database;

        private readonly AsyncLock _redisAsyncLock = new AsyncLock();

        public IDatabase Database
        {
            get
            {
                return _database;
            }
        }

        private readonly IRedisCacheDatabaseProvider _redisCacheDatabaseProvider;

        public RedisCache(IRedisCacheDatabaseProvider redisCacheDatabaseProvider) : base("TongTongMall.RedisCache")
        {
            _redisCacheDatabaseProvider = redisCacheDatabaseProvider;
            _database = redisCacheDatabaseProvider.GetDatabase();

        }

        public RedisCache(string name, IRedisCacheDatabaseProvider redisCacheDatabaseProvider) : base(name)
        {
            _redisCacheDatabaseProvider = redisCacheDatabaseProvider;
            _database = redisCacheDatabaseProvider.GetDatabase();

        }


        public long GetNextSequence(string key, TimeSpan? timeSpan = null)
        {
            return _database.GetNextSequence(key, timeSpan);
        }

        public override object GetOrDefault(string key)
        {
            var obj = _database.StringGet(GetLocalizedKey(key));
            if (obj.HasValue)
            {
                var item = JsonConvert.DeserializeObject<RedisCacheItem>(obj);
                return JsonConvert.DeserializeObject(item.Item, item.Type);
            }
            return null;
        }

        public override void Set(string key, object value, TimeSpan? slidingExpireTime = null)
        {
            if (value == null)
            {
                throw new TongTongMallException("Can not insert null values to the cache!");
            }

            var cacheItem = new RedisCacheItem { Type = value.GetType(), Item = JsonConvert.SerializeObject(value) };
            _database.StringSet(
                GetLocalizedKey(key),
                JsonConvert.SerializeObject(cacheItem),
                slidingExpireTime ?? DefaultSlidingExpireTime
                );
        }

        public override void Remove(string key)
        {
            _database.KeyDelete(GetLocalizedKey(key));
        }

        public override void Clear()
        {
            _database.KeyDeleteWithPrefix(GetLocalizedKey("*"));
        }

        public List<string> GetPatterKeys(string pattern)
        {
            var keys = GetHashPatterKeys(pattern + "*");
            return keys.Select(x => x.ToString()).ToList();
        }

        public void DeletePatternKey(string pattern)
        {
            var keys = GetHashPatterKeys(pattern);
            Database.KeyDelete(keys.Select(x => (RedisKey)x).ToArray());

        }

        public async Task DeletePatternKeyAsync(string pattern)
        {
            var keys = GetHashPatterKeys(pattern);
            await Database.KeyDeleteAsync(keys.ToArray());
        }

        public bool Exists(string key)
        {
            return Database.KeyExists(key);
        }

        public Task<bool> ExistsAsync(string key)
        {
            return Database.KeyExistsAsync(key);
        }

        private string GetLocalizedKey(string key)
        {
            return key;
        }

        public void SetExpiration(string key, TimeSpan expiresIn)
        {
            if (expiresIn.Ticks < 0)
                this.Remove(key);
            Database.KeyExpire(key, expiresIn);
        }

        public Task SetExpirationAsync(string key, TimeSpan expiresIn)
        {
            if (expiresIn.Ticks < 0)
                this.RemoveAsync(key);
            return Database.KeyExpireAsync(key, expiresIn);
        }

        public Task<TimeSpan?> GetExpirationAsync(string key)
        {
            return Database.KeyTimeToLiveAsync(key);
        }

        public TimeSpan? GetExpiration(string key)
        {
            return Database.KeyTimeToLive(key);
        }

        public override T GetOrDefault<T>(string key)
        {

            var obj = _database.StringGet(GetLocalizedKey(key));
            if (obj.HasValue)
            {
                var item = JsonConvert.DeserializeObject<RedisCacheItem>(obj);
                return (T)JsonConvert.DeserializeObject(item.Item, item.Type);
            }

            return default(T);
        }

        public async Task<T> GetOrAddAsync<T>(string key, Func<string, Task<T>> factory, TimeSpan? expire)
        {
            if (!_redisCacheDatabaseProvider.IsReadForCache)
            {
               return await factory(key);
            }
            var cacheKey = key;
            var item = await GetOrDefaultAsync(key);
            if (item == null)
            {
                using (await _redisAsyncLock.LockAsync())
                {
                    item = await GetOrDefaultAsync(key);
                    //关闭缓存时，统一返回Null
                    if (item == null)
                    {
                        item = await factory(key);
                        if (item == null)
                        {
                            return default(T);
                        }
                        await SetAsync(cacheKey, item, expire);
                    }
                }
            }
            return (T)item;
        }


        #region Sets
        public async Task<bool> SetAddAsync(string key, params string[] values)
        {
            return await Database.SetAddAsync(key, values.Select(x => (RedisValue)x).ToArray()) > 0;
        }
        public async Task<bool> SetContainsAsync(string key, string value)
        {
            return await Database.SetContainsAsync(key, value);
        }
        public async Task<bool> SetRemoveAsync(string key, params string[] values)
        {
            return await Database.SetRemoveAsync(key, values.Select(x => (RedisValue)x).ToArray()) > 0;
        }

        #endregion

        #region SortedSet
        public async Task<bool> SortedSetAddAsync(string key, IDictionary<string, double> values)
        {
            return await Database.SortedSetAddAsync(key, values.Select(x => new SortedSetEntry(x.Key, x.Value)).ToArray()) > 0;
        }
        public async Task<string[]> SortedSetRangeByScoreAsync(string key, double start = double.NegativeInfinity, double stop = double.PositiveInfinity, long skip = 0, long take = -1)
        {
            return (await Database.SortedSetRangeByScoreAsync(key, start, stop, Exclude.Both, Order.Ascending, skip, take)).Select(x => x.ToString()).ToArray();
        }
        public async Task<bool> SortedSetRemoveAsync(string key, params string[] values)
        {
            return await Database.SortedSetRemoveAsync(key, values.Select(x => (RedisValue)x).ToArray()) > 0;
        }
        //public async Task<bool> SortedSetRemoveRangeByValueAsync(string key, string value)
        //{
        //    return await Database.SortedSetRemoveRangeByValueAsync(key, value, value, Exclude.Both) > 0;
        //}
        #endregion

        #region Lists
        public async Task<bool> ListRightPushAsync(string key, params string[] values)
        {
            return await Database.ListRightPushAsync(key, values.Select(x => (RedisValue)x).ToArray()) > 0;
        }
        public async Task<bool> ListLeftPushAsync(string key, params string[] values)
        {
            return await Database.ListLeftPushAsync(key, values.Select(x => (RedisValue)x).ToArray()) > 0;
        }
        public async Task<string> ListLeftPopAsync(string key)
        {
            return await Database.ListLeftPopAsync(key);
        }
        #endregion

        private HashSet<RedisKey> GetHashPatterKeys(string pattern)
        {
            var keys = new HashSet<RedisKey>();

            var servers = _redisCacheDatabaseProvider.GetServer();
            foreach (var server in servers)
            {
                var dbKeys = server.Keys(Database.Database, pattern);
                foreach (var dbKey in dbKeys)
                {
                    if (!keys.Contains(dbKey))
                    {
                        keys.Add(dbKey);
                    }
                }
            }
            return keys;
        }
    }
}
